import { color, composeColors } from './../../core/color.js';
import { cloudflareLogger } from '../../core/constants.js';
import { IS_WINDOWS, waitForExit } from './utils.js';
import { spawn, execSync } from 'node:child_process';
import fs from 'node:fs';
import https from 'node:https';
import path from 'node:path';

const CLOUDFLARED_VERSION = process.env.CLOUDFLARED_VERSION || "latest";
const DEFAULT_BIN_PATH = path.join(process.cwd(), ".robo", "bin", IS_WINDOWS ? "cloudflared.exe" : "cloudflared");
const RELEASE_BASE = "https://github.com/cloudflare/cloudflared/releases/";
const Ignore = ["https://api.trycloudflare.com"];
class UnsupportedError extends Error {
  constructor(message) {
    super(message);
    this.name = "UnsupportedError";
  }
}
const LINUX_URL = {
  arm64: "cloudflared-linux-arm64",
  arm: "cloudflared-linux-arm",
  x64: "cloudflared-linux-amd64",
  ia32: "cloudflared-linux-386"
};
const MACOS_URL = {
  // Yeah, still no Apple Silicon support... https://github.com/cloudflare/cloudflared/issues/389
  arm64: "cloudflared-darwin-amd64.tgz",
  x64: "cloudflared-darwin-amd64.tgz"
};
const WINDOWS_URL = {
  x64: "cloudflared-windows-amd64.exe",
  ia32: "cloudflared-windows-386.exe"
};
function resolveBase(version) {
  if (version === "latest") {
    return `${RELEASE_BASE}latest/download/`;
  }
  return `${RELEASE_BASE}download/${version}/`;
}
async function installCloudflared(to = DEFAULT_BIN_PATH, version = CLOUDFLARED_VERSION) {
  if (process.platform === "linux") {
    return installLinux(to, version);
  } else if (process.platform === "darwin") {
    return installMacos(to, version);
  } else if (process.platform === "win32") {
    return installWindows(to, version);
  } else {
    throw new UnsupportedError("Unsupported platform: " + process.platform);
  }
}
function isCloudflaredInstalled(to = DEFAULT_BIN_PATH) {
  return fs.existsSync(to);
}
function startCloudflared(url) {
  const ESCAPED_BIN_PATH = IS_WINDOWS ? `"${DEFAULT_BIN_PATH}"` : DEFAULT_BIN_PATH;
  cloudflareLogger.event(`Starting tunnel...`);
  cloudflareLogger.debug(ESCAPED_BIN_PATH + " tunnel --url " + url);
  const childProcess = spawn(ESCAPED_BIN_PATH, ["tunnel", "--url", url, "--no-autoupdate"], {
    shell: IS_WINDOWS,
    stdio: "pipe"
  });
  let lastMessage = "";
  const onData = (data) => {
    lastMessage = data.toString()?.trim();
    cloudflareLogger.debug(color.dim(lastMessage));
    const tunnelUrl = extractTunnelUrl(lastMessage);
    if (tunnelUrl && !Ignore.includes(tunnelUrl) && !lastMessage.includes("Request failed")) {
      cloudflareLogger.ready(`Tunnel URL:`, composeColors(color.bold, color.blue)(tunnelUrl));
    }
  };
  childProcess.stdout.on("data", onData);
  childProcess.stderr.on("data", onData);
  childProcess.on("exit", (code) => {
    if (code !== 0) {
      cloudflareLogger.error(lastMessage ?? "Failed to start tunnel.");
    }
  });
  return childProcess;
}
function stopCloudflared(child, signal = "SIGINT") {
  child?.kill(signal);
  return waitForExit(child).then(() => {
    cloudflareLogger.debug("Tunnel stopped.");
  });
}
function extractTunnelUrl(output) {
  const regex = /https:\/\/[a-zA-Z0-9.-]*\.trycloudflare.com/;
  const match = output.match(regex);
  return match ? match[0] : null;
}
async function installLinux(to, version = CLOUDFLARED_VERSION) {
  const file = LINUX_URL[process.arch];
  if (file === void 0) {
    throw new UnsupportedError("Unsupported architecture: " + process.arch);
  }
  await download(resolveBase(version) + file, to);
  fs.chmodSync(to, "755");
  return to;
}
async function installMacos(to, version = CLOUDFLARED_VERSION) {
  const file = MACOS_URL[process.arch];
  if (file === void 0) {
    throw new UnsupportedError("Unsupported architecture: " + process.arch);
  }
  await download(resolveBase(version) + file, `${to}.tgz`);
  cloudflareLogger.debug(`Extracting to ${to}`);
  execSync(`tar -xzf ${path.basename(`${to}.tgz`)}`, { cwd: path.dirname(to) });
  fs.unlinkSync(`${to}.tgz`);
  fs.renameSync(`${path.dirname(to)}/cloudflared`, to);
  return to;
}
async function installWindows(to, version = CLOUDFLARED_VERSION) {
  const file = WINDOWS_URL[process.arch];
  if (file === void 0) {
    throw new UnsupportedError("Unsupported architecture: " + process.arch);
  }
  await download(resolveBase(version) + file, to);
  return to;
}
function download(url, to, redirect = 0) {
  if (redirect === 0) {
    cloudflareLogger.debug(`Downloading ${url} to ${to}`);
  } else {
    cloudflareLogger.debug(`Redirecting to ${url}`);
  }
  if (!fs.existsSync(path.dirname(to))) {
    fs.mkdirSync(path.dirname(to), { recursive: true });
  }
  return new Promise((resolve, reject) => {
    const request = https.get(url, (res) => {
      const redirect_code = [301, 302, 303, 307, 308];
      if (redirect_code.includes(res.statusCode) && res.headers.location !== void 0) {
        request.destroy();
        const redirection = res.headers.location;
        resolve(download(redirection, to, redirect + 1));
        return;
      }
      if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
        const file = fs.createWriteStream(to);
        file.on("finish", () => {
          file.close(() => resolve(to));
        });
        file.on("error", (err) => {
          fs.unlink(to, () => reject(err));
        });
        res.pipe(file);
      } else {
        request.destroy();
        reject(new Error(`HTTP response with status code: ${res.statusCode}`));
      }
    });
    request.on("error", (err) => {
      reject(err);
    });
    request.end();
  });
}

export { installCloudflared, isCloudflaredInstalled, startCloudflared, stopCloudflared };
